﻿// -------------------------------------------------------------------------------------------------------
// LICENSE INFORMATION
//
// - This software is licensed under the MIT shared source license.
// - The "official" source code for this project is maintained at http://mtcagent.codeplex.com
//
// Copyright (c) 2010 OpenNETCF Consulting
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 
// associated documentation files (the "Software"), to deal in the Software without restriction, 
// including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial 
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 
// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
// -------------------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OpenNETCF.MTConnect;
using System.Xml.Linq;
using System.Net;
using System.Threading;
using OpenNETCF.IoC;
using System.IO;
using System.Reflection;
using System.Diagnostics;
using Microsoft.Win32;
using OpenNETCF.Web.Server;

namespace OpenNETCF.MTConnect
{
    internal class PadarnService : MTConnectProvider, IDisposable
    {
        private OpenNETCF.Web.Server.WebServer m_server;
        private bool m_firstRun = true;

        public string ConfigurationFile { get; private set; }
        public const string DefaultConfigFileName = "DeviceConfig.xml";

        [InjectionConstructor]
        public PadarnService([ServiceDependency]IConfigurationService config)
            : base(10000, 1000)
        {
            m_server = new WebServer();
            Debug.WriteLine(string.Format("Padarn IS{0} licensed", m_server.IsLicensed ? string.Empty : " NOT"));
 
            this.Host.RequestReceived += new EventHandler<GenericEventArgs<IResourceRequest>>(Host_RequestReceived);
        }

        void Host_RequestReceived(object sender, GenericEventArgs<IResourceRequest> e)
        {
            RaiseStatusUpdateRequested(
                e.Value.RequestSource,
                string.Format("{0} received for {1}", e.Value.GetType().Name, e.Value.ResourceName));

            if (!m_server.IsLicensed)
            {
                Debug.WriteLine(string.Format("*** Padarn IS{0} licensed ***", m_server.IsLicensed ? string.Empty : " NOT"));
            }
        }

        public void Dispose()
        {
            Stop();
        }

        public override void OnStart()
        {


            if (m_server.Running) return;

            if (m_firstRun)
            {
                LoadPersistedConfiguration();
                m_firstRun = false;
            }

            var hostedAdapterService = RootWorkItem.Services.Get<IHostedAdapterService>();
            hostedAdapterService.StartAdapterPublishing();

            m_server.Start();
            RaiseStatusUpdateRequested("[local]", "Agent Started.");
            RaiseStatusUpdateRequested("[local]", "Virtual Agent running at: " + AgentAddress);
        }

        public override void Stop()
        {
            if (!m_server.Running) return;

            //RaiseStatusUpdateRequested("[local]", "Agent Stopping.");
            m_server.Stop();
        }

        public override bool Running
        {
            get { return m_server.Running; }
        }

        protected override void OnSetAdapterConfiguration(string requestSource, string xml)
        {
            var doc = XDocument.Parse(xml);
            SetAdapterConfiguration(requestSource, doc, true);
        }

        private void SetAdapterConfiguration(string requestSource, XDocument doc, bool persist)
        {
            RaiseStatusUpdateRequested(requestSource, "New Configuration Received");
                    
            var deviceElements = ConfigParser.GetDeviceElements(doc);

            this.Agent.Adapters.Clear();

            foreach (var element in deviceElements)
            {
                var adapter = new XmlDefinedAdapter(element);
                this.Agent.Adapters.Add(adapter);
            }

            if (persist)
            {
                PersistConfiguration(doc);
            }

            foreach (var adapter in this.Agent.Adapters)
            {
                adapter.PublishDefaultData();
            }

        }

        private void PublishAllDefaultData(AdapterCollection dac)
        {
            lock (dac.SyncRoot)
            {
                foreach (var adapter in dac)
                {
                    //Device Data
                    foreach (var item in adapter.Device.DataItems)
                    {
                        PublishDefaultData(item);
                    }

                    //Component Data
                    foreach (var component in adapter.Device.Components)
                    {
                        foreach (var dataItem in component.DataItems)
                        {
                            PublishDefaultData(dataItem);
                        }

                        //Subcomponent Data
                        foreach (var subcomponent in component.Components)
                        {
                            foreach (var dataItem in subcomponent.DataItems)
                            {
                                PublishDefaultData(dataItem);
                            }
                        }
                    }
                }
            }
        }

        private void PublishDefaultData(DataItem dataItem)
        {
//            Debug.WriteLine(string.Format("PadarnService::PublishDefaultData({0})", dataItem.ID));

            //If we encounter a system type we Like
            switch (dataItem.Type.ToUpper())
            {
                case "SYSTEM.DOUBLE": this.Agent.PublishData(dataItem.ID, "0.0", DateTime.Now); return;
                case "SYSTEM.BOOLEAN": this.Agent.PublishData(dataItem.ID, "false", DateTime.Now); return;
                case "SYSTEM.STRING": this.Agent.PublishData(dataItem.ID, "", DateTime.Now); return;
                case "SYSTEM.INT64": this.Agent.PublishData(dataItem.ID, "0", DateTime.Now); return;
                case "AVAILABILITY": this.Agent.PublishData(dataItem.ID, "AVAILABLE", DateTime.Now); return;
                default: return;
            }
        }

        private void CheckPersistenceFile()
        {
            var config = RootWorkItem.Services.Get<IConfigurationService>().AgentConfiguration;

            string filePath;

            try
            {
                filePath = Path.GetFullPath(config.DeviceConfigurationFile);
            }
            catch
            {
                filePath = Path.GetDirectoryName(Assembly.GetCallingAssembly().GetName().CodeBase);
                if (filePath.StartsWith("file:\\"))
                {
                    filePath = filePath.Substring(6);
                }
                filePath = Path.Combine(filePath, DefaultConfigFileName);
            }

            ConfigurationFile = filePath;

            var folder = Path.GetDirectoryName(filePath);

            if (!Directory.Exists(folder))
            {
                Directory.CreateDirectory(folder);
            }
            if (!File.Exists(filePath))
            {
                var doc = new XDocument(new XDeclaration("1.0", "utf-8", "yes"),
                    new XElement("Devices"));

                doc.Save(filePath);
            }
        }

        private void LoadPersistedConfiguration()
        {
            CheckPersistenceFile();

            if(!File.Exists(ConfigurationFile)) return;

            XDocument doc;
            
            try
            {
                doc = XDocument.Load(ConfigurationFile);
            }
            catch(Exception ex)
            {
                Debug.WriteLine("PadarnService::LoadPersistedConfiguration threw while loading config file: " + ex.Message);
                return;
            }

            SetAdapterConfiguration("[persisted config]", doc, false);

            RaiseAdapterConfigurationChanged();
        }

        private void PersistConfiguration(XDocument doc)
        {
            CheckPersistenceFile();

            doc.Save(ConfigurationFile);
        }

        public override Adapter[] GetAdapters()
        {
            if (Agent.Adapters == null)
            {
                return new Adapter[] { };
            }

            return Agent.Adapters.ToArray();
        }

        protected override IHost GetHost(Agent agent)
        {
            return new PadarnHost(agent);
        }

        public override void SetDataByDataItemID(string requestSource, string dataItemID, string data)
        {
            // first make sure the ID exists
            if (Agent.Adapters == null) return; // No Adapters yet!

            var dataItem = Agent.GetDataItemByID(dataItemID);

            if (dataItem == null)
            {
                throw new InvalidIDException(dataItemID, string.Format("DataItem with ID {0} not found", dataItemID));
            }
            if (!dataItem.Writable)
            {
                throw new InvalidOperationException(string.Format("DataItem with ID {0} is not writable", dataItemID));
            }

            RaiseStatusUpdateRequested(requestSource, string.Format("New Data: ID {0} set to {1}", dataItemID, data));

            dataItem.SetValue(data);
        }

        public override string AgentAddress
        {
            get
            {
                string ip;
                if (m_server.Configuration.LocalIP.Equals(IPAddress.Any))
                {
                    ip = (from a in Dns.GetHostEntry(Dns.GetHostName()).AddressList
                          where a.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork
                          select a).First().ToString();
                }
                else
                {
                    ip = m_server.Configuration.LocalIP.ToString();
                }

                return string.Format("{0}:{1}",
                    ip,
                    m_server.Configuration.Port);
            }
        }
    }
}
